#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <errno.h>

#include "broadcast_proto.h"
#include "debug.h"
#include "des.h"

const static sproto_header broadcast_header = 
{
	.type = BROADCAST_PROTO_TYPE,
	.name = BROADCAST_PROTO_NAME,
	.version = BROADCAST_PROTO_VER,
	.length = sizeof(sbroadcast_content),
	.build = broadcast_build_package,
	.parse = broadcast_parse_package
};
const sproto_header *broadcast_default_header = &broadcast_header;

static unsigned char cmd_maps[][2] = 
{// {down-cmd,need-up-cmd}
	{0x60,0x71},
	{0x61,0x72},
	{0x62,0x73},
	{0x63,0x74},
	{0x64,0x75}
};
static unsigned char response_cmd(unsigned char cmd)
{
	int res_cmd = 0;
	int size = sizeof(cmd_maps)/2;
	int i;

	for(i=0;i<size;i++)
	{
		if(cmd == cmd_maps[i][0])
		{
			res_cmd = cmd_maps[i][1];
			break;
		}
	}

	return res_cmd;
}

static int build_up_package(unsigned int cmd,ubroadcast_up_content*content,unsigned char *buff,int *len)
{
	int ret = 0, i = 0;
	unsigned char tmp[64]={0};
	
	memset(buff,0,*len);
	switch(cmd)
	{
		case BROADCAST_RES_STATUS:
			*len = snprintf((char*)buff,*len,"%s;%s;%s;%s;%s;%s;%hu;%s;%hu;%d",
					content->res_dev_status.id,content->res_dev_status.ip,
					content->res_dev_status.subnet,content->res_dev_status.gwip,
					content->res_dev_status.dnsip,content->res_dev_status.version,
					content->res_dev_status.web_port,content->res_dev_status.macaddr,
					content->res_dev_status.stream_port,content->res_dev_status.dhcp_flag);
			*len += 1;
			break;
		case BROADCAST_RES_SETOK:
			*len = snprintf((char*)buff,*len,"%s",content->res_setok_id);
			*len += 1;
			break;
		case BROADCAST_RES_CODE:
			i = snprintf((char*)tmp,64,"%s;%s;%s",content->res_get_code.id,
					content->res_get_code.name,content->res_get_code.code);
			ret = desEncode((unsigned char*)DES_SUBKEY, strlen(DES_SUBKEY),tmp,i,buff,*len);
			if(ret < 0)
			{
				PLOG(ERR,"desEncode BROADCAST_RES_CODE error!\n");
				ret = VPI_EFAIL;
			}
			while((i%8)!=0)
				i++;
			*len = i;
			break;
		case BROADCAST_RES_P2P:
			*len = snprintf((char*)buff,*len,"%s;%s;%s;%s;%s;%s;%hu;%s;%hu",
					content->res_p2p_status.id,content->res_p2p_status.ip,
					content->res_p2p_status.subnet,content->res_p2p_status.gwip,
					content->res_p2p_status.dnsip,content->res_p2p_status.version,
					content->res_p2p_status.web_port,content->res_p2p_status.macaddr,
					content->res_p2p_status.stream_port);
			*len += 1;
			break;
		case BROADCAST_RES_NET:
			*len = snprintf((char*)buff,*len,"%s",content->res_setnet_id);
			*len += 1;
			break;
		default:
			ret = VPI_EFAIL;
			break;
	}

	return ret;
}

static int build_down_package(unsigned int cmd,ubroadcast_down_content*content,unsigned char *buff,int *len)
{
	int ret = 0,i = 0;
	
	memset(buff,0,*len);
	switch(cmd)
	{
		case BROADCAST_GET_STATUS: // nothing
			break;
		case BROADCAST_SET_STATUS:
			*len += snprintf((char*)buff,*len,"%s;%s;%s;%s;%s;%s;%s",
					content->set_status.id,content->set_status.ip,
					content->set_status.subnet,content->set_status.gwip,
					content->set_status.dnsip,content->set_status.username,
					content->set_status.code);
			*len += 1;
			break;
		case BROADCAST_GET_CODE:
			i = strlen(content->get_ident_domain);
			ret = desEncode((unsigned char*)DES_SUBKEY, strlen(DES_SUBKEY),(unsigned char *)content->get_ident_domain,i,buff,*len);
			if(ret < 0)
			{
				PLOG(ERR,"desEncode in BROADCAST_GET_CODE error!\n");
				ret = VPI_EFAIL;
			}
			while((i%8)!=0)
				i++;
			*len = i;
			break;
		case BROADCAST_GET_P2P:// do nothing
			break;
		case BROADCAST_SET_NET_PARAS:
			memcpy(buff,content->set_net_para.id,9);
			buff[9] = content->set_net_para.dhcp_flag;
			buff[10] = content->set_net_para.stream_port&0xff;
			buff[11] = (content->set_net_para.stream_port>>8)&0xff;
			buff[12] = content->set_net_para.web_port&0xff;
			buff[13] = (content->set_net_para.web_port>>8)&0xff;
			*len = 14;
			break;

		default:
			ret = VPI_EFAIL;
			break;
	}
	return ret;
}

static int parse_up_package(unsigned int cmd,ubroadcast_up_content*content,unsigned char *buff,int len)
{
	int ret = 0,num = 0;
	unsigned char tmp[64]={0};
	unsigned char ck_sum = buff[len];///!preserve checksum to restore it  
	
	memset(content,0,sizeof(ubroadcast_up_content));
	switch(cmd)
	{
		case BROADCAST_RES_STATUS:
			buff[len] = 0;// ensure string ends with null byte
			num = sscanf((char*)buff,"%[^;];%[^;];%[^;];%[^;];%[^;];%[^;];%hu;%[^;];%hu;%hhu",
					content->res_dev_status.id,content->res_dev_status.ip,
					content->res_dev_status.subnet,content->res_dev_status.gwip,
					content->res_dev_status.dnsip,content->res_dev_status.version,
					&content->res_dev_status.web_port,content->res_dev_status.macaddr,
					&content->res_dev_status.stream_port,&content->res_dev_status.dhcp_flag);
			buff[len] = ck_sum;/// restore checksum
			if(num < 10)
			{
				PLOG(ERR,"BROADCAST_RES_STATUS cmd parse error!\n");
				ret = VPI_EPARAMS;
			}
			break;
		case BROADCAST_RES_SETOK:
			memcpy(content->res_setnet_id,buff,len);
			break;
		case BROADCAST_RES_CODE:
			ret = desDecode((unsigned char*)DES_SUBKEY, strlen(DES_SUBKEY),(unsigned char *)buff,len, tmp,sizeof(tmp));
			if(ret < 0)
			{
				PLOG(ERR,"desDecode in BROADCAST_RES_CODE error!\n");
				ret = VPI_EFAIL;
			}
			else
			{
				num = sscanf((char*)tmp,"%[^;];%[^;];%s",content->res_get_code.id,
						content->res_get_code.name,content->res_get_code.code);
				if(num < 3)
				{
					PLOG(ERR,"BROADCAST_RES_CODE cmd parse error!\n");
					ret = VPI_EPARAMS;
				}
			}
			break;
		case BROADCAST_RES_P2P:
			buff[len] = 0;// ensure string ends with null byte
			num = sscanf((char*)buff,"%[^;];%[^;];%[^;];%[^;];%[^;];%[^;];%hu;%[^;];%hu",
					content->res_p2p_status.id,content->res_p2p_status.ip,
					content->res_p2p_status.subnet,content->res_p2p_status.gwip,
					content->res_p2p_status.dnsip,content->res_p2p_status.version,
					&content->res_p2p_status.web_port,content->res_p2p_status.macaddr,
					&content->res_p2p_status.stream_port);
			buff[len] = ck_sum;/// restore checksum
			if(num < 9)
			{
				PLOG(ERR,"BROADCAST_RES_P2P cmd parse error!\n");
				ret = VPI_EPARAMS;
			}
			break;
		case BROADCAST_RES_NET:
			memcpy(content->res_setnet_id,buff,len);
			break;
		default:
			ret = VPI_EFAIL;
			break;
	}

	return ret;
}

static int parse_down_package(unsigned int cmd,ubroadcast_down_content*content,unsigned char *buff,int len)
{
	int ret = 0,num = 0;
	unsigned char ck_sum = buff[len];///!preserve checksum to restore it  
	
	memset(content,0,sizeof(ubroadcast_down_content));
	switch(cmd)
	{
		case BROADCAST_GET_STATUS:// no data
			break;
		case BROADCAST_SET_STATUS:
			buff[len] = 0;// ensure string ends with null byte
			num = sscanf((char*)buff,"%[^;];%[^;];%[^;];%[^;];%[^;];%[^;];%s",
					content->set_status.id,content->set_status.ip,
					content->set_status.subnet,content->set_status.gwip,
					content->set_status.dnsip,content->set_status.username,
					content->set_status.code);
			buff[len] = ck_sum;/// restore checksum
			if(num < 7)
			{
				PLOG(ERR,"BROADCAST_SET_STATUS cmd parse error!\n");
				ret = VPI_EPARAMS;
			}
			break;
		case BROADCAST_GET_CODE:
			ret = desDecode((unsigned char*)DES_SUBKEY, strlen(DES_SUBKEY),\
					(unsigned char *)buff,len,(unsigned char*)content->get_ident_domain,\
					sizeof(content->get_ident_domain));
			if(ret < 0)
			{
				PLOG(ERR,"desDecode BROADCAST_GET_CODE content error!\n");
				memset(content->get_ident_domain,0,sizeof(content->get_ident_domain));
				ret = VPI_EFAIL;
			}
			//memcpy(content->get_ident_domain,buff,len);
			break;
		case BROADCAST_GET_P2P:// no data
			break;
		case BROADCAST_SET_NET_PARAS:
			memcpy(content->set_net_para.id,buff,9);
			content->set_net_para.dhcp_flag = buff[9];
			content->set_net_para.stream_port = buff[10]+(buff[11]<<8);
			content->set_net_para.web_port = buff[12] + (buff[13]<<8);
			break;

		default:
			ret = VPI_EFAIL;
			break;
	}

	return ret;
}

int broadcast_build_package(eproto_role role,sproto_msg *pmsg,unsigned char *sendbuff, int *len)
{
	int ret = 0;
	unsigned char buf_tmp[512] = {0};
	int tmplen = 512;
	unsigned char *tmpbuf = &buf_tmp[0];
	unsigned int cmd = pmsg->cmd;
	sbroadcast_content *content =(sbroadcast_content *)pmsg->content;

	if(content == NULL || sendbuff == NULL || len == NULL)
	{
		PLOG(ERR,"invalid params!\n");
		return VPI_EPARAMS;
	}
	if(*len >= tmplen)
	{
		tmpbuf = sendbuff;
		tmplen = *len;
	}
	if(IS_BROADCAST_UPCMD(cmd)&&role==ROLE_DEV)
	{
		tmpbuf[0] = BROADCAST_TOP_1;
		tmpbuf[1] = BROADCAST_TOP_2;
		tmpbuf[2] = tmpbuf[3] = 0xff;// pack length
		tmpbuf[4] = cmd&0xff;
		tmplen -= 7;// 5+checksum+end
		ret = build_up_package(cmd,&content->up_content,tmpbuf+5,&tmplen);
		if(ret == 0)
		{
			tmplen += 7;
			tmpbuf[2] = tmplen&0xff;
			tmpbuf[3] = (tmplen>>8)&0xff;
			tmpbuf[tmplen-2] = vpi_calc_checksum(tmpbuf,tmplen-2);
			tmpbuf[tmplen-1] = BROADCAST_END;
		}
	}
	else if(IS_BROADCAST_DOWNCMD(cmd)&&role==ROLE_SERVER)
	{
		tmpbuf[0] = BROADCAST_TOP_1;
		tmpbuf[1] = BROADCAST_TOP_2;
		tmpbuf[2] = tmpbuf[3] = 0xff;// pack length
		tmpbuf[4] = cmd&0xff;
		tmplen -= 7;// 5+checksum+end
		ret = build_down_package(cmd,&content->down_content,tmpbuf+5,&tmplen);
		if(ret == 0)
		{
			tmplen += 7;
			tmpbuf[2] = tmplen&0xff;
			tmpbuf[3] = (tmplen>>8)&0xff;
			tmpbuf[tmplen - 2] = vpi_calc_checksum(tmpbuf,tmplen-2);
			tmpbuf[tmplen - 1] = BROADCAST_END;
		}
	}
	else
	{
		PLOG(ERR,"cmd 0x%x and role %d is unsupported!\n",cmd,role);
		return VPI_EFAIL;
	}

	if(!ret && *len >= tmplen)
	{// buff is enough big
		PLOG(DBG,"send buff len = %d.\n", tmplen);
		if(tmpbuf != sendbuff)
		{
			memset(sendbuff,0,*len);
			memcpy(sendbuff,tmpbuf,tmplen);
		}
		*len = tmplen;
		vpi_print_package(sendbuff,*len);
	}
	else if(!ret && *len < tmplen)
	{
		PLOG(ERR,"send buff is not enough,%d < %d[need]\n",*len,tmplen);
		ret = VPI_ESPACE;
	}

	return ret;
}

int broadcast_parse_package(eproto_role role,sproto_msg *pmsg,unsigned char *recvbuff, int len)
{
	unsigned int *cmd = &pmsg->cmd;
	sbroadcast_content *content = (sbroadcast_content *)pmsg->content;
	int data_len = 0;
	int ret = 0;

	if(content == NULL || recvbuff == NULL || len <= 0)
	{
		PLOG(ERR, "invalid params!\n");
		return VPI_EPARAMS;
	}
	
	vpi_print_package(recvbuff,len);
	if(role==ROLE_DEV)
	{
		if(recvbuff[0]!=BROADCAST_TOP_1|| recvbuff[1]!=BROADCAST_TOP_2|| 
			recvbuff[len-1]!=BROADCAST_END) 
		{
			PLOG(ERR,"recv package is invalid in device role.\n");
			ret = VPI_EFORMAT;
		}
		else if(recvbuff[len-2]!=vpi_calc_checksum(recvbuff,len-2))
		{
			PLOG(ERR,"recv package checksum error!\n");
			ret = VPI_ECHECKSUM;
		}
		else
		{
			PLOG(DBG,"recv server ok,parse the content...\n");
			*cmd = recvbuff[4];
			///! the length in recvbuff is total pack len.
			data_len = recvbuff[2] + (recvbuff[3]<<8) - 7;
			PLOG(DBG,"recv cmd = 0x%02x, data len = %d\n",*cmd,data_len);
			if(data_len > 0) // have params
				ret = parse_down_package(*cmd,&content->down_content,recvbuff+5,data_len);
		}
	}
	else if(role==ROLE_SERVER)
	{
		if(recvbuff[0]!=BROADCAST_TOP_1|| recvbuff[1]!=BROADCAST_TOP_2||
			recvbuff[len-1]!=BROADCAST_END)
		{
			PLOG(ERR,"recv package is invalid in server role.\n");
			ret = VPI_EFORMAT;
		}
		else if(recvbuff[len-2]!=vpi_calc_checksum(recvbuff,len-2))
		{
			PLOG(ERR,"recv package checksum error!\n");
			ret = VPI_ECHECKSUM;
		}
		else if(IS_BROADCAST_DOWNCMD(*cmd) && recvbuff[4]!=response_cmd(*cmd))
		{
			PLOG(ERR,"recv cmd 0x%02x is not respond to send cmd 0x%02x\n",recvbuff[4],*cmd);
			ret = VPI_ERESPCMD;
		}
		else
		{
			PLOG(DBG,"recv device ok,parse the content...\n");
			*cmd = recvbuff[4];
			data_len = recvbuff[2] + (recvbuff[3]<<8) - 7;
			PLOG(DBG,"recv cmd = 0x%02x, data len = %d\n",*cmd,data_len);
			if(data_len > 0) // have params
				ret = parse_up_package(*cmd,&content->up_content,recvbuff+5,data_len);
		}
	}
	else
	{
		PLOG(ERR,"role %d or cmd 0x%x is unsupported!\n",role,*cmd);
		ret = VPI_EFAIL;
	}

	return ret;
}
